//
//  HTTPPromise.m
//  ProductionFrame
//
//  Created by weiyin on 15/10/10.
//  Copyright © 2015年 weiyin. All rights reserved.
//

#import "HTTPPromise.h"

#pragma mark method signature
struct AXTBlockLiteral {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved;	// NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

typedef NS_OPTIONS(NSUInteger, AXTBlockDescriptionFlags) {
    AXTBlockDescriptionFlagsHasCopyDispose = (1 << 25),
    AXTBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code
    AXTBlockDescriptionFlagsIsGlobal = (1 << 28),
    AXTBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    AXTBlockDescriptionFlagsHasSignature = (1 << 30)
};

static NSMethodSignature *NSMethodSignatureForBlock(id block) {
    if (!block)
        return nil;
    
    struct AXTBlockLiteral *blockRef = (__bridge struct AXTBlockLiteral *)block;
    AXTBlockDescriptionFlags flags = (AXTBlockDescriptionFlags)blockRef->flags;
    
    if (flags & AXTBlockDescriptionFlagsHasSignature) {
        void *signatureLocation = blockRef->descriptor;
        signatureLocation += sizeof(unsigned long int);
        signatureLocation += sizeof(unsigned long int);
        
        if (flags & AXTBlockDescriptionFlagsHasCopyDispose) {
            signatureLocation += sizeof(void(*)(void *dst, void *src));
            signatureLocation += sizeof(void (*)(void *src));
        }
        
#if __IPHONE_OS_VERSION_MIN_REQUIRED <= __IPHONE_5_1
        // since in iOS 5.x, passing signature with type infomation, like '@"AXTPromise"8@?0@4',
        // which returns an AXTPromise and carries no argument to NSMethodSignature raises exception,
        // and we don't actually need the type info, hence remove it.
        
        const char *signatureWithType = (*(const char **)signatureLocation);
        NSMutableString *signature = [NSMutableString stringWithCString:signatureWithType encoding:NSASCIIStringEncoding];
        NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\"[^\"]*\""
                                                                               options:kNilOptions
                                                                                 error:NULL];
        [regex replaceMatchesInString:signature
                              options:kNilOptions
                                range:NSMakeRange(0, [signature length])
                         withTemplate:@""];
        return [NSMethodSignature signatureWithObjCTypes:[signature cStringUsingEncoding:NSASCIIStringEncoding]];
#else
        const char *signature = (*(const char **)signatureLocation);
        //NSMethodSignature顾名思义应该就是“方法签名”
        return [NSMethodSignature signatureWithObjCTypes:signature];
#endif
    }
    return 0;
}

#pragma mark -
@interface HTTPPromise ()
@property (strong, nonatomic) id value;
@property (strong, nonatomic) NSMutableArray *steps;
@end

@implementation HTTPPromise

#pragma mark basic
static void AXTPromiseResolve(HTTPPromise *promise) {
    while ([promise.steps count] > 0) {
        // forward one step
        id block = promise.steps[0];
        [promise.steps removeObjectAtIndex:0];
        HTTPPromiseSafeCall(promise, block);
        
        if (![promise.value isKindOfClass:[HTTPPromise class]]) {
            continue;
        } else if (promise.value == promise) {
            promise.value = nil;
        } else {
            HTTPPromise *new = promise.value;
            [promise.steps addObjectsFromArray:new.steps];
            new.steps = promise.steps;
            promise.steps = nil;
            break;
        }
    }
}

static void HTTPPromiseSafeCall(HTTPPromise *promise, id block) {
    NSMethodSignature *signature = NSMethodSignatureForBlock(block);
    if (signature.methodReturnType[0] == 'v') {
        if (signature.numberOfArguments == 1) { // argument 0 is the block, parameters begin at index 1
            void (^safeBlock)() = block;
            safeBlock();
        } else {
            void (^safeBlock)(id) = block;
            safeBlock(promise.value);
        }
    } else {
        if (signature.numberOfArguments == 1) { // argument 0 is the block, parameters begin at index 1
            id (^safeBlock)() = block;
            promise.value = safeBlock();
        } else {
            id (^safeBlock)(id) = block;
            promise.value = safeBlock(promise.value);
        }
    }
}

#pragma mark life cycle
+ (HTTPPromise *)promise {
    return [self promiseWithValue:nil];
}

+ (HTTPPromise *)promiseWithValue:(id)object {
    return [HTTPPromise new:^(void (^fulfiller)(id), void (^rejecter)(NSError *)) {
        [[NSBlockOperation blockOperationWithBlock:^{
            fulfiller(object);
        }] performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
    }];
}

+ (HTTPPromise *)new:(void (^)(void (^fulfiller)(id), void (^rejecter)(NSError *)))block {
    HTTPPromise *promise = [[self alloc] init];
    void (^fulfiller)(id result) = ^(id result) {
        promise.value = result;
        AXTPromiseResolve(promise);
    };
    void (^rejecter)(NSError *error) = ^(NSError *error) {
        promise.value = error;
        AXTPromiseResolve(promise);
    };
    if (block) block(fulfiller, rejecter);
    return promise;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.steps = [NSMutableArray array];
    }
    return self;
}

- (void)dealloc {
    //
}

#pragma mark state
- (BOOL)pending {
    return self.value == nil;
}

- (BOOL)resolved {
    if ([self.value isKindOfClass:[HTTPPromise class]]) {
        return [self.value resolved];
    } else {
        return [self.steps count] == 0;
    }
}

- (HTTPPromise *)activePromise {
    if ([self.value isKindOfClass:[HTTPPromise class]]) {
        return [self.value activePromise];
    } else {
        return self;
    }
}

- (HTTPPromise * (^)(id))then {
    if ([self.value isKindOfClass:[HTTPPromise class]]) {
        return ((HTTPPromise *)self.value).then;
    }
    if (self.value) {
        return [HTTPPromise promiseWithValue:self.value].then;
    }
    
    return ^(id handler) {
        if (!handler) return self;
        
        __block HTTPPromise *SELF = self;
        [self.steps addObject:^() {
            HTTPPromise *promise = [SELF activePromise];
            if (![promise.value isKindOfClass:[NSError class]]) {
                HTTPPromiseSafeCall(promise, handler);
            }
        }];
        return self;
    };
}

- (HTTPPromise * (^)(id block))catch {
    if ([self.value isKindOfClass:[HTTPPromise class]]) {
        return ((HTTPPromise *)self.value).then;
    }
    if (self.value) {
        return [HTTPPromise promiseWithValue:self.value].then;
    }
    
    return ^(id handler) {
        if (!handler) return self;
        
        __block HTTPPromise *SELF = self;
        [self.steps addObject:^() {
            HTTPPromise *promise = [SELF activePromise];
            if ([promise.value isKindOfClass:[NSError class]]) {
                HTTPPromiseSafeCall(promise, handler);
            }
        }];
        return self;
    };
}

#pragma mark control
- (void)cancel {
    [self.steps removeAllObjects];
}

@end

#pragma mark - convenience method
void HTTPWaitPromise(HTTPPromise *promise) {
    while (![promise resolved]) {
        BOOL executed = NO;
        if ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode // allow GCD to run
                                     beforeDate:[NSDate distantFuture]]) {
            executed = YES;
        }
        if ([[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes // allow network io
                                     beforeDate:[NSDate distantFuture]]) {
            executed = YES;
        }
        if (!executed) {
            break;
        }
    }
}
